#!/usr/bin/env python
## Author:      Samuel Abels
## Date:        2007-06-04
## Description: Starts the Exscript interpreter to execute commands on a list 
##              of hosts.
import sys, os, re, getpass
import termconnect
sys.path.append('src')
from Exscript        import Exscript, Account, TemplateRunner, util
from Exscript.FooLib import Interact
from Exscript.FooLib import OptionParser

True  = 1
False = 0

__version__ = '0.9.16'

def usage():
    print "Exscript", __version__, "with termconnect", termconnect.__version__
    print "Copyright (C) 2007-2009 by Samuel Abels <http://debain.org>."
    print "Syntax: exscript [options] exscript [hostname [hostname ...]]"
    print "      --account-pool FILE"
    print "                 Reads the user/password combination from the given file "
    print "                 instead of prompting on the command line. The file may"
    print "                 also contain more than one user/password combination, in"
    print "                 which case the accounts are used round robin."
    print "  -c, --connections NUM"
    print "                 Maximum number of concurrent connections."
    print "                 NUM is a number between 1 and 20, default is 1"
    print "      --csv-hosts FILE"
    print "                 Loads a list of hostnames and definitions from the given file."
    print "                 The first line of the file must have the column headers in the"
    print "                 following syntax:"
    print "                    hostname [variable] [variable] ..."
    print "                 where the fields are separated by tabs, \"hostname\" is the"
    print "                 keyword \"hostname\" and \"variable\" is a unique name under"
    print "                 which the column is accessed in the script."
    print "                 The following lines contain the hostname in the first column,"
    print "                 and the values of the variables in the following columns."
    print "  -d, --define PAIR"
    print "                 Defines a variable that is passed to the script."
    print "                 PAIR has the following syntax: <STRING>=<STRING>."
    print "      --default-domain STRING"
    print "                 The IP domain name that is used if a given hostname has no "
    print "                 domain appended."
    print "      --delete-logs"
    print "                 Delete logs of successful operations when done."
    print "  -e, --execute EXSCRIPT"
    print "                 Interpretes the given string as the script."
    print "      --hosts FILE"
    print "                 Loads a list of hostnames from the given file (one host per"
    print "                 line)."
    print "  -i, --non-interactive"
    print "                 Do not ask for a username or password."
    print "  -l, --logdir DIR"
    print "                 Logs any communication into the directory with the given name."
    print "                 Each filename consists of the hostname with \"_log\" appended."
    print "                 Errors are written to a separate file, where the filename"
    print "                 consists of the hostname with \".log.error\" appended."
    print "      --no-echo"
    print "                 Turns off the echo, such that the network activity is no longer"
    print "                 written to stdout."
    print "                 This is already the default behavior if the -c option was given"
    print "                 with a number greater than 1."
    print "  -n, --no-authentication"
    print "                 When given, the authentication procedure is skipped. Implies -i."
    print "      --no-auto-logout"
    print "                 Do not attempt to execute the exit or quit command at the end"
    print "                 of a script."
    print "      --no-prompt"
    print "                 Do not wait for a prompt anywhere. Note that this will also"
    print "                 cause Exscript to disable commands that require a prompt, such"
    print "                 as 'extract'."
    print "      --no-initial-prompt"
    print "                 Do not wait for a prompt after sending the password."
    print "      --no-strip"
    print "                 Do not strip the first line of each response."
    print "      --overwrite-logs"
    print "                 Instructs Exscript to overwrite existing logfiles. The default"
    print "                 is to append the output if a log already exists."
    print "  -p, --protocol STRING"
    print "                 Specify which protocol to use to connect to the remote host."
    print "                 STRING is one of: telnet ssh"
    print "                 The default protocol is telnet."
    print "      --retry-login NUM"
    print "                 Defines the number of retries on login failure. Default is 0."
    print "      --sleep TIME"
    print "                 Waits for the specified time before running the script."
    print "                 TIME is a timespec as specified by the 'sleep' Unix command."
    print "      --ssh-auto-verify"
    print "                 Automatically confirms the 'Host key changed' SSH error "
    print "                 message with 'yes'. Highly insecure and not recommended."
    print "      --ssh-key FILE"
    print "                 Specify a key file that is passed to the SSH client."
    print "                 This is equivalent to using the -i parameter with ssh."
    print "  -v, --verbose NUM"
    print "                 Print out debug information about the network activity."
    print "                 NUM is a number between 0 (min) and 5 (max)"
    print "  -V, --parser-verbose NUM"
    print "                 Print out debug information about the Exscript parser."
    print "                 NUM is a number between 0 (min) and 5 (max)"
    print "      --version  Prints the version number."
    print "  -h, --help     Prints this help."

# Define default options.
default_defines = {'hostname': 'unknown'}
default_options = [
  ('no-echo',           None, False),
  ('account-pool=',     None, None),
  ('connections=',      'c:', 1),
  ('csv-hosts=',        None, None),
  ('define=',           'd:', default_defines),
  ('delete-logs',       None, False),
  ('execute=',          'e:', None),
  ('hosts=',            None, None),
  ('default-domain=',   None, ''),
  ('non-interactive',   'i',  False),
  ('logdir=',           'l:', None),
  ('no-authentication', 'n',  False),
  ('no-prompt',         None, False),
  ('no-initial-prompt', None, False),
  ('no-auto-logout',    None, False),
  ('overwrite-logs',    None, False),
  ('verbose=',          'v:', 0),
  ('parser-verbose=',   'V:', 0),
  ('protocol=',         'p:', 'telnet'),
  ('retry-login=',      None, 0),
  ('sleep=',            None, ''),
  ('ssh-auto-verify',   None, False),
  ('ssh-key=',          None, None),
  ('no-strip',          None, False),
  ('version',           None, False),
  ('help',              'h',  False)
]

def run(*args, **kwargs):
    options = {}
    for option, short_option, value in default_options:
        option = re.sub(r'=$', '', option)
        options[option] = value
    options.update(kwargs)
    options['define'].update(default_defines)

    # Show the help, if requested.
    if options['help']:
        usage()
        sys.exit()

    # Show the version number, if requested.
    if options['version']:
        print "Exscript", __version__, "with termconnect", termconnect.__version__
        sys.exit()

    # Extract the hostnames out of the command line arguments.
    try:
        filename  = args[0]
        hostnames = list(args[1:])
    except:
        usage()
        sys.exit(1)
    trunner = TemplateRunner(verbose        = options.get('verbose'),
                             domain         = options.get('default-domain'),
                             retry_login    = options.get('retry-login'),
                             overwrite_logs = options.get('overwrite-logs'),
                             delete_logs    = options.get('delete-logs'),
                             strip_command  = not options.get('no-strip'),
                             logdir         = options.get('logdir'))
    if options.get('execute') is not None:
        trunner.add_host(filename)
        trunner.add_hosts(hostnames)
    else:
        trunner.add_hosts(hostnames)

    # If a filename containing hostnames AND VARIABLES was given, read it.
    if options.get('csv-hosts') is not None:
        try:
            hosts = util.get_hosts_from_csv(options.get('csv-hosts'))
        except Exception, e:
            print e
            sys.exit(1)
        trunner.add_hosts(hosts)

    # If a filename containing hostnames was given, read it.
    if options.get('hosts') is not None:
        try:
            hosts = util.get_hosts_from_file(options.get('hosts'))
        except Exception, e:
            print e
            sys.exit(1)
        trunner.add_hosts(hosts)

    # Create the log directory.
    if options.get('logdir') is not None:
        if not os.path.exists(options.get('logdir')):
            print 'Creating log directory (%s)...' % options.get('logdir')
            try:
                os.makedirs(options.get('logdir'))
            except:
                print 'Error: Unable to create directory %s.' % options.get('logdir')
                sys.exit(1)

    # Make sure that all mandatory options are present.
    if len(trunner.hosts) <= 0:
        usage()
        sys.exit(1)

    # Read the Exscript.
    if options.get('execute') is not None:
        trunner_content = options.get('execute')
    else:
        file            = open(filename, 'r')
        trunner_content = file.read()
        trunner.file    = filename
        file.close()

    # Prepare the code that is executed after the user script has completed.
    #FIXME: Move into the library, then use read_template_from_file instead of read_template().
    if not options['no-auto-logout']:
        trunner_content += r'''
    ## Exscript generated commands. ##
    {if device.guess_os() is "vrp"}
        {connection.send("quit\r", 0)}
    {else}
        {connection.send("exit\r", 0)}
    {end}'''

    # Load the code into the trunner.
    trunner.define(**options['define'])
    try:
        trunner.read_template(trunner_content)
    except Exception, e:
        if options['verbose'] > 0:
            raise
        print e
        sys.exit(1)

    # Create Exscript.
    exscript = Exscript(verbose     = options['verbose'],
                        max_threads = options['connections'])
    account_manager = exscript.account_manager

    # Read the account pool file.
    try:
        if options['account-pool']:
            accounts = util.get_accounts_from_file(options['account-pool'])
            account_manager.add_account(accounts)
    except Exception, e:
        print e
        sys.exit(1)
    if options['account-pool'] and account_manager.n_accounts() == 0:
        msg = r'WARNING: No accounts found in account pool file (%s)!'
        print msg % options['account-pool']

    # Read username and password.
    try:
        if options['non-interactive'] or account_manager.n_accounts() > 0:
            user     = None
            password = None
        else:
            user, password = Interact.get_login()
    except:
        sys.exit(1)
    if user is not None:
        account = Account(user, password, ssh_key_file = options['ssh-key'])
        account_manager.add_account(account)

    # Ask for values of marked variables.
    for key, val in options['define'].iteritems():
        match = TemplateRunner.bracket_expression_re.match(val)
        if match is None:
            continue
        str = match.group(1) or 'a value for "%s"' % key
        if str.startswith('!'):
            val = getpass.getpass('Please enter %s: ' % str[1:])
        else:
            val = raw_input('Please enter %s: ' % str)
        options['define'][key] = val

    # Configure the template runner.
    del options['hosts']
    trunner.set_options(retry_login    = options.get('retry-login'),
                        parser_verbose = options.get('parser-verbose'),
                        no_prompt      = options.get('no-prompt'),
                        strip_command  = not options.get('no-strip'),
                        **options)

    # Wait until the specified time.
    if options['sleep']:
        print "Waiting for %s..." % options['sleep'],
        sys.stdout.flush()
        os.system('sleep %s' % options['sleep'])
        print "time expired, starting script."

    exscript.run(trunner)

if __name__ == '__main__':
    # Parse options.
    try:
        options, args = OptionParser.parse_options(sys.argv, default_options)
    except:
        usage()
        sys.exit(1)

    run(*args, **options)
